package com.angel.arquitectura.flex.reflect;

import com.angel.arquitectura.flex.hibernate.HibernateUtils;
import org.apache.commons.collections15.CollectionUtils;
import org.apache.commons.collections15.Predicate;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;

public class ReflectionUtils {
    public static <T> T createInstance(Class<T> aClass) {
        try {
            return aClass.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /**
     * Determina si un tipo es instanciable o no (en otras palabras, si es una clase
     * concreta)
     *
     * @param aClass el tipo
     * @return true si es instanciable
     */
    public static <T> boolean isInstantiable(Class<T> aClass) {
        int modifiers = aClass.getModifiers();
        return !Modifier.isAbstract(modifiers) && !Modifier.isInterface(modifiers);
    }

//    /**
//     * Setea el valor de una propiedad JavaBean. Si no la propiedad no existe, no se
//     * tira exception.
//     *
//     * @param instance     el objeto
//     * @param propertyName el nombre de la propiedad
//     * @param value        el objeto a setear
//     */
//    public static void setPropertyValue(Object instance, String propertyName, Object value) {
//        BeanProxy.BeanProperty property = getBeanProperty(instance.getClass(), propertyName);
//        if (property != null) {
//            property.set(instance, value);
//        }
//    }
//
//    private static BeanProxy.BeanProperty getBeanProperty(Class aClass, String propertyName) {
//        BeanInfo beanInfo = null;
//        try {
//            beanInfo = Introspector.getBeanInfo(aClass);
//        } catch (IntrospectionException e) {
//            throw new RuntimeException(e);
//        }
//        PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors();
//        for (int i = 0; i < descriptors.length; i++) {
//            PropertyDescriptor descriptor = descriptors[i];
//            if (descriptor.getName().equals(propertyName)) {
//                return new BeanProxy.BeanProperty(descriptor);
//            }
//        }
//        return null;
//    }
//
//    /**
//     * Setea el valor de una propiedad, chequeando previamente si se puede hacer. Si no, se realizan las
//     * conversiones que apliquen.
//     *
//     * @param instance     la instancia
//     * @param propertyName la propiedad a setear
//     * @param value        el valor
//     */
//    public static void setPropertyValueNonStrictly(Object instance, String propertyName, Object value) {
//        BeanProxy.BeanProperty property = getBeanProperty(instance.getClass(), propertyName);
//        if (!property.getType().isAssignableFrom(value.getClass())) {
//            Object convertedValue = doConversion(value, value.getClass(), property.getType());
//            if (convertedValue != null) {
//                setPropertyValue(instance, propertyName, convertedValue);
//                return;
//            }
//        }
//        setPropertyValue(instance, propertyName, value);
//    }
//
//    private static Object doConversion(Object value, Class<? extends Object> sourceType, Class destinationType) {
//        if (sourceType == Integer.class && destinationType == Long.class) {
//            Integer integer = (Integer) value;
//            return new Long(integer.longValue());
//        }
//        return null;
//    }
//
//    public static Class<?> getReturnValueTypeParameter(Method readMethod) {
//        Type type = readMethod.getGenericReturnType();
//        if (type == null) {
//            return null;
//        }
//        if (type instanceof ParameterizedType) {
//            ParameterizedType parametrizedType = (ParameterizedType) type;
//            Type actualType = parametrizedType.getActualTypeArguments()[0];
//            if (actualType instanceof Class) {
//                return (Class) actualType;
//            }
//        }
//        return null;
//    }
//
    public static boolean isAnnotationPresentInActualClass(Object propertyOwner, Method method, Class<? extends Annotation> annotationClass) {
        try {
            Method actualMethod = HibernateUtils.getActualClass(propertyOwner).getMethod(method.getName(), method.getParameterTypes());
            return actualMethod.isAnnotationPresent(annotationClass);
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        }
    }

    public static Field getFieldByName(Class<?> clazz, String name) {
        try {
            return clazz.getDeclaredField(name);
        } catch (NoSuchFieldException e) {
            throw new RuntimeException("No existe el campo '" + name +
                    "' en un objeto de clase " + clazz.getName(), e);
        }
    }

    public static void setFieldValue(Object fieldOwner, String name, Object value) {
        setFieldValue(fieldOwner, getFieldByName(fieldOwner.getClass(), name), value);
    }

    public static void setFieldValue(Object fieldOwner, Field field, Object value) {
        try {
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            field.set(fieldOwner, value);
            field.setAccessible(accessible);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Object getFieldValue(Object fieldOwner, String name) {
        return getFieldValue(fieldOwner, getFieldByName(fieldOwner.getClass(), name));
    }

    public static Object getFieldValue(Object fieldOwner, Field field) {
        try {
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            Object result = field.get(fieldOwner);
            field.setAccessible(accessible);
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean isAnnotationPresent(Collection<Annotation> annotationList, final Class<? extends Annotation> annotationClass) {
        return getAnnotationOfType(annotationList,  annotationClass) != null;
    }

    @SuppressWarnings("unchecked")
    public static <T extends Annotation> T getAnnotationOfType(Collection<Annotation> annotationList, final Class<? extends Annotation> annotationClass) {
        return (T) CollectionUtils.find(annotationList, new Predicate<Annotation>() {
            public boolean evaluate(Annotation annotation) {
                return annotationClass.isAssignableFrom(annotation.getClass());
            }
        });
    }
}